﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Threading;

namespace NeuralNetworks
{
    public partial class ASCIIUserControl : UserControl
    {
        #region Constructors

        public ASCIIUserControl()
        {
            this.InitializeComponent();
            this.InitializeTrainingData();
            this.InitializeUntrainedData();
            this.InitializeRectangles();
        }

        #endregion

        #region Private Members

        private List<InputRectangle> _rectangles;
        private short[][] _input;
        private short[][] _output;
        private char[] _chars;
        private short[][] _untraintedInput;
        private short[][] _untraintedOutput;
        private char[] _untraintedChars;
        private int _totalLetters;
        private Thread _trainingThread;

        #endregion

        #region Events

        public event EventHandler TrainingFinished;

        #endregion

        #region Attributes and Properties

        private NeuralNetwork _network;
        public NeuralNetwork Network
        {
            get { return _network; }
            set { _network = value; }
        }

        #endregion

        #region Private Methods

        private void InitializeRectangles()
        {
            int width = _letterPanel.ClientRectangle.Width / 6 - 1;
            int height = _letterPanel.ClientRectangle.Height / 8 - 1;

            _rectangles = new List<InputRectangle>();

            for (int i = 0; i < 8; i++)
            {
                for (int j = 0; j < 6; j++)
                    _rectangles.Add(new InputRectangle(new Rectangle(width * j, height * i, width, height)));
            }
        }

        private void InitializeTrainingData()
        {
            string[] lines = Properties.Resources.ASCII.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            _totalLetters = lines.Count();
            _input = new short[lines.Count()][];
            _output = new short[lines.Count()][];
            _chars = new char[lines.Count()];

            lines.For((line, i) =>
            {
                _input[i] = new short[48];
                _output[i] = new short[7];

                string[] properties = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                for (int j = 0; j < 48; j++)
                    _input[i][j] = Convert.ToInt16(properties[2][j].ToString());

                for (int j = 0; j < 7; j++)
                    _output[i][j] = Convert.ToInt16(properties[1][j].ToString());

                _chars[i] = (char)Convert.ToInt32(properties[0]);
            });

            _trainingProgressBar.Maximum = _totalLetters;
        }

        private void InitializeUntrainedData()
        {
            _untraintedInput = new short[5][];
            _untraintedOutput = new short[5][];
            _untraintedChars = new char[5];
            _untraintedInput[0] = new short[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1 };
            _untraintedOutput[0] = new short[] { 1, 0, 0, 0, 0, 0, 0 };
            _untraintedChars[0] = 'a';
            _untraintedInput[1] = new short[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0 };
            _untraintedOutput[1] = new short[] { 1, 0, 0, 0, 0, 0, 1 };
            _untraintedChars[1] = 'b';
            _untraintedInput[2] = new short[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 };
            _untraintedOutput[2] = new short[] { 1, 0, 0, 0, 0, 1, 0 };
            _untraintedChars[2] = 'c';
            _untraintedInput[3] = new short[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 };
            _untraintedOutput[3] = new short[] { 1, 0, 0, 0, 0, 1, 1 };
            _untraintedChars[3] = 'd';
            _untraintedInput[4] = new short[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 };
            _untraintedOutput[4] = new short[] { 1, 0, 0, 0, 1, 0, 0 };
            _untraintedChars[4] = 'e';
        }

        private void TrainNeuralNetwork()
        {
            this.SetTrainingProgressBarValue(0);
            this.SetTrainingResultsLabelText(string.Empty);
            this.UpdateTrainingControls(true);

            short[] result;
            float oldHigherError = 0f;
            int i = 0;
            var before = DateTime.Now;
            do
            {
                _network.ResetHigherErrorFactor();
                int c = 0;

                for (int j = 0; j < _totalLetters; j++)
                {
                    oldHigherError = _network.HigherErrorFactor;
                    result = _network.Train(_input[j], _output[j]);
                    if (_network.LastHigherError <= 0.1f)
                        c++;
                    if (_network.HigherErrorFactor > 0.1 && oldHigherError <= 0.1)
                        Console.WriteLine(_chars[j]);
                }

                this.SetTrainingProgressBarValue(c);

                i++;
            }
            while (_network.HigherErrorFactor > 0.1f && i < 2000);
            var after = DateTime.Now;
            var time = after - before;

            this.SetTrainingResultsLabelText(string.Format("Treinado com {0} iterações em {1},{2} segundos.", i, (int)time.TotalSeconds, (int)time.Milliseconds));
            this.UpdateTrainingControls(false);

            this.RaiseTrainingFinished();

            _trainingThread = null;
        }

        private void Calculate()
        {
            if (_trainingThread != null)
                return;

            short[] selected = new short[8 * 6];
            for (int i = 0; i < 8 * 6; i++)
                selected[i] = (short)(_rectangles[i].Selected == true ? 1 : 0);

            short[] result = _network.Calculate(selected);

            char charFound = this.GetChar(result);

            _resultTextBox.Text = charFound.ToString();
        }

        private char GetChar(short[] result)
        {
            char? charFound = null;
            for (int i = 0; i < _totalLetters && charFound == null; i++)
            {
                bool allEqual = true;

                for (int j = 0; j < 7 && allEqual; j++)
                {
                    if (result[j] != _output[i][j])
                        allEqual = false;
                }

                if (allEqual)
                    charFound = _chars[i];
            }

            if (charFound == null)
                for (int i = 0; i < 5 && charFound == null; i++)
                {
                    bool allEqual = true;

                    for (int j = 0; j < 7 && allEqual; j++)
                    {
                        if (result[j] != _untraintedOutput[i][j])
                            allEqual = false;
                    }

                    if (allEqual)
                        charFound = _untraintedChars[i];
                }

            if (charFound.HasValue)
                return charFound.Value;

            return new char();
        }

        private void SetTrainingResultsLabelText(string text)
        {
            if (_trainingResultsLabel.InvokeRequired)
                _trainingResultsLabel.Invoke(new SetTrainingResultsLabelTextDelegate(SetTrainingResultsLabelText), text);
            else
                _trainingResultsLabel.Text = text;
        }

        private void UpdateTrainingControls(bool trainingStarted)
        {
            if (_trainingProgressBar.InvokeRequired)
                _trainingProgressBar.Invoke(new UpdateTrainingControlsDelegate(UpdateTrainingControls), trainingStarted);
            else
            {
                _trainingProgressBar.Visible = trainingStarted;
                _stopButton.Visible = trainingStarted;
                _trainButton.Enabled = !trainingStarted;
                _calculateButton.Enabled = !trainingStarted;
            }
        }

        private void SetTrainingProgressBarValue(int value)
        {
            if (_trainingProgressBar.InvokeRequired)
                _trainingProgressBar.Invoke(new SetTrainingProgressBarValueDelegate(SetTrainingProgressBarValue), value);
            else
                _trainingProgressBar.Value = value;
        }

        private short[] CopyArray(short[] array)
        {
            short[] copy = new short[array.Length];
            for (int i = 0; i < array.Length; i++)
                copy[i] = array[i];

            return copy;
        }

        #endregion

        #region Signed Events Methods

        private void TrainButton_Click(object sender, EventArgs e)
        {
            _trainingThread = new Thread(TrainNeuralNetwork);
            _trainingThread.Start();
        }

        private void CalculateButton_Click(object sender, EventArgs e)
        {
            this.Calculate();
        }

        private void LetterPanel_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.FillRectangle(Brushes.White, e.ClipRectangle);

            foreach (var rectangle in _rectangles)
                rectangle.Draw(e.Graphics);
        }

        private void LetterPanel_MouseMove(object sender, MouseEventArgs e)
        {
            foreach (var rectangle in _rectangles)
                rectangle.ProcessMouseMove(e.Location);

            _letterPanel.Invalidate();
        }

        private void LetterPanel_MouseUp(object sender, MouseEventArgs e)
        {
            foreach (var rectangle in _rectangles)
                rectangle.ProcessMouseClick(e.Button, e.Location);
        }

        private void InputChar_TextChanged(object sender, EventArgs e)
        {
            short[] selected = null;

            if (_inputChar.Text.Count() > 0)
            {
                for (int i = 0; i < _totalLetters && selected == null; i++)
                {
                    if (_chars[i] == _inputChar.Text[0])
                        selected = _input[i];
                }
            }

            if (selected != null)
            {
                for (int i = 0; i < 8 * 6; i++)
                    _rectangles[i].Selected = selected[i] == 1 ? true : false;
            }
            else
            {
                for (int i = 0; i < 8 * 6; i++)
                    _rectangles[i].Selected = false;
            }

            _letterPanel.Invalidate();
        }

        private void RaiseTrainingFinished()
        {
            if (this.TrainingFinished != null)
                this.TrainingFinished(this, new EventArgs());
        }

        private void StopButton_Click(object sender, EventArgs e)
        {
            if (_trainingThread == null)
                return;

            _trainingThread.Abort();
            _trainingThread = null;
            this.UpdateTrainingControls(false);
            this.RaiseTrainingFinished();
        }

        private void InputChar_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
                this.Calculate();
        }

        private string ArrayToString(short[] array)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < array.Length; i++)
            {
                sb.Append(array[i].ToString());
            }

            return sb.ToString();
        }

        private void GenerateReportButton_Click(object sender, EventArgs e)
        {
            List<string> log = new List<string>();
            short[] result;
            int c = 1;
            Action<char, string, char, string> writeToLog = (c1, s1, c2, s2) =>
                {
                    log.Add(string.Format("{0:000}: Testando '{1}'({2})... Encontrado '{3}'({4}).", c, c1, s1, c2, s2));
                };

            for (int i = 0; i < _totalLetters; i++)
            {
                result = _network.Calculate(_input[i]);
                char expectedChar = _chars[i];
                char foundChar = this.GetChar(result);

                writeToLog(expectedChar, this.ArrayToString(_output[i]), foundChar, this.ArrayToString(result));
                c++;
            }

            Random random = new Random();
            for (int i = 0; i < 6; i++)
            {
                int index = random.Next(0, _totalLetters - 1);
                result = _network.Calculate(_input[index]);
                char expectedChar = _chars[index];
                char foundChar = this.GetChar(result);

                writeToLog(expectedChar, this.ArrayToString(_output[i]), foundChar, this.ArrayToString(result));
                c++;
            }

            for (int i = 0; i < 10; i++)
            {
                int index = random.Next(0, _totalLetters - 1);
                short[] input = this.CopyArray(_input[index]);

                int noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;

                result = _network.Calculate(input);
                char expectedChar = _chars[index];
                char foundChar = this.GetChar(result);

                log.Add(string.Format("{0:000}: Testando '{1}'({2}) com 1 ruído... Encontrado '{3}'({4}).", c, expectedChar, this.ArrayToString(_output[index]), foundChar, this.ArrayToString(result)));
                c++;
            }

            for (int i = 0; i < 10; i++)
            {
                int index = random.Next(0, _totalLetters - 1);
                short[] input = this.CopyArray(_input[index]);

                int noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;
                noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;

                result = _network.Calculate(input);
                char expectedChar = _chars[index];
                char foundChar = this.GetChar(result);

                log.Add(string.Format("{0:000}: Testando '{1}'({2}) com 2 ruídos... Encontrado '{3}'({4}).", c, expectedChar, this.ArrayToString(_output[index]), foundChar, this.ArrayToString(result)));
                c++;
            }

            for (int i = 0; i < 5; i++)
            {
                int index = random.Next(0, _totalLetters - 1);
                short[] input = this.CopyArray(_input[index]);

                int noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;
                noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;
                noiseIndex = random.Next(0, 47);
                input[noiseIndex] = input[noiseIndex] == 0 ? (short)1 : (short)0;

                result = _network.Calculate(input);
                char expectedChar = _chars[index];
                char foundChar = this.GetChar(result);

                log.Add(string.Format("{0:000}: Testando '{1}'({2}) com 3 ruídos... Encontrado '{3}'({4}).", c, expectedChar, this.ArrayToString(_output[index]), foundChar, this.ArrayToString(result)));
                c++;
            }

            for (int i = 0; i < 5; i++)
            {
                result = _network.Calculate(_untraintedInput[i]);
                char expectedChar = _untraintedChars[i];
                char foundChar = this.GetChar(result);

                log.Add(string.Format("{0:000}: Testando '{1}'({2}) não treinado... Encontrado '{3}'({4}).", c, expectedChar, this.ArrayToString(_untraintedOutput[i]), foundChar, this.ArrayToString(result)));
                c++;
            }

            File.WriteAllLines("report.txt", log);
        }

        #endregion
    }

    public class InputRectangle
    {
        #region Constructors

        public InputRectangle(Rectangle rectangle)
        {
            _rectangle = rectangle;
        }

        #endregion

        #region Private Members

        private Rectangle _rectangle;
        private bool _hovering;

        #endregion

        #region Attributes and Properties

        private bool _selected;
        public bool Selected
        {
            get { return _selected; }
            set { _selected = value; }
        }

        #endregion

        #region Public Methods

        public void ProcessMouseMove(Point mouseLocation)
        {
            _hovering = _rectangle.Contains(mouseLocation);
        }

        public void ProcessMouseClick(MouseButtons mouseButton, Point mouseLocation)
        {
            if (mouseButton == MouseButtons.Left && _rectangle.Contains(mouseLocation))
                _selected = !_selected;
        }

        public void Draw(Graphics g)
        {
            if (_hovering || _selected)
                g.FillRectangle(_selected ? Brushes.Black : Brushes.LightGray, _rectangle);

            g.DrawRectangle(Pens.Black, _rectangle);
        }

        #endregion
    }

    public class DoubleBufferedRadPanel : Panel
    {
        #region Constructors

        public DoubleBufferedRadPanel()
            : base()
        {
            this.SetStyle(
                   ControlStyles.UserPaint |
                   ControlStyles.AllPaintingInWmPaint |
                   ControlStyles.OptimizedDoubleBuffer,
                   true);
        }

        #endregion
    }
}
